﻿using Autofac;
using AutoRegistDependency.Component;
using AutoRegistDependency.Interface;
using AutoRegistDependency.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace AutoRegistDependency.Implement
{
    /// <summary>
    /// 事件总线默认实现
    /// </summary>
    public class EventBus : IEventBus
    {
        private readonly IComponentContext componentContext;
        private readonly ConcurrentDictionary<Type, List<Type>> handlerInterfaceMap = new ConcurrentDictionary<Type, List<Type>>();
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="componentContext">组件上下文</param>
        public EventBus(IComponentContext componentContext)
        {
            this.componentContext = componentContext;
            var register = this.componentContext.Resolve<Register>();
            var registMap = register.TypeMapInfo;
            foreach (ComponentDefinition component in registMap.Values.SelectMany(t => t))
            {
                if (component.EventBusInterfaces.Count() == 0)
                {
                    continue;
                }
                else
                {
                    foreach (Type eventBusInterface in component.EventBusInterfaces)
                    {
                        if (handlerInterfaceMap.TryGetValue(eventBusInterface, out List<Type> result))
                        {
                            result.Add(component.InjectType);
                        }
                        else
                        {
                            handlerInterfaceMap.TryAdd(eventBusInterface, new List<Type>() { component.InjectType });
                        }
                    }
                }
            }
        }
        public void Publish<T>(T eventData)
        {
            List<Exception> exceptions = new List<Exception>();
            var services = GetServices<T>(typeof(IEventHandler<>));
            foreach (var group in services.GroupBy(t => t.GetType().FullName))
            {
                try
                {
                    group.FirstOrDefault()?.Handle(eventData);
                }
                catch(Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions.ToArray());
            }
        }

        public List<TResult> Publish<T, TResult>(T eventData)
        {
            var services = GetServices<T, TResult>(typeof(IEventHandler<,>));
            List<Exception> exceptions = new List<Exception>();
            List<TResult> result = new List<TResult>();
            foreach (var group in services.GroupBy(t => t.GetType().FullName))
            {
                try
                {
                    result.Add(group.FirstOrDefault()?.Handle(eventData));
                }
                catch(Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            if(exceptions.Count > 0)
            {
                throw new AggregateException(exceptions.ToArray());
            }
            return result;

        }

        public async Task PublishAsync<T>(T eventData)
        {
            var services = GetServices<T>(typeof(IEventAsyncHandler<>));
            List<Exception> exceptions = new List<Exception>();
            foreach (var group in services.GroupBy(t => t.GetType().FullName))
            {
                try
                {
                    await group.FirstOrDefault()?.Handle(eventData);
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
            }
            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions.ToArray());
            }
        }

        public async Task<List<TResult>> PublishAsync<T, TResult>(T eventData)
        {
            var services = GetServices<T, TResult>(typeof(IEventAsyncHandler<,>));
            List<Exception> exceptions = new List<Exception>();
            List<TResult> result = new List<TResult>();
            foreach (var group in services.GroupBy(t => t.GetType().FullName))
            {
                try
                {
                    result.Add(await group.FirstOrDefault()?.Handle(eventData));
                }
                catch (Exception ex)
                {
                    exceptions.Add(ex);
                }
                
            }
            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions.ToArray());
            }
            return result;
        }
        /// <summary>
        /// 接口服务解析可用此方法(接口的服务解析出来是个数组,类是object)
        /// </summary>
        /// <param name="types"></param>
        /// <returns></returns>
        private IEnumerable<dynamic> GetResolveServices(IEnumerable<Type> types)
        {
            if (types.Any(t => !t.IsInterface))
            {
                throw new ArgumentException("所有的type必须为interface！！");
            }
            return types.SelectMany(t => (IEnumerable<dynamic>)componentContext.Resolve(t)).Distinct();
        }

        private IEnumerable<dynamic> GetServices<T>(Type handerInterfaceType)
        {
            List<dynamic> services = new List<dynamic>();
            var genericTypes = new List<Type> { typeof(IEnumerable<>), handerInterfaceType };
            Type eventType = typeof(T);
            var eventInterfaceTypes = eventType.GetTypeInfo().ImplementedInterfaces.ToList();
            eventInterfaceTypes.Add(eventType);
            List<Type> handerTypes = new List<Type>();
            foreach (Type type in eventInterfaceTypes)
            {
                var handerType = AutoFacHelper.GetGenericType(new List<Type>() { handerInterfaceType }, type);
                handerTypes.Add(AutoFacHelper.GetGenericType(genericTypes, type));
                if (handlerInterfaceMap.TryGetValue(handerType, out List<Type> result))
                {
                    services.AddRange(result.Select(t => componentContext.Resolve(t)));
                }
            }
            services.AddRange(GetResolveServices(handerTypes));
            return services;
        }

        private IEnumerable<dynamic> GetServices<T, TResult>(Type handerInterfaceType)
        {
            var genericTypes = new List<Type> { typeof(IEnumerable<>), handerInterfaceType };
            Type eventType = typeof(T);
            var eventInterfaceTypes = eventType.GetTypeInfo().ImplementedInterfaces.ToList();
            eventInterfaceTypes.Add(eventType);
            List<Type> handerTypes = new List<Type>();
            List<dynamic> services = new List<dynamic>();
            foreach (Type type in eventInterfaceTypes)
            {
                var handerType = AutoFacHelper.GetGenericType(new List<Type>() { handerInterfaceType }, type, typeof(TResult));
                handerTypes.Add(AutoFacHelper.GetGenericType(genericTypes, type, typeof(TResult)));
                if (handlerInterfaceMap.TryGetValue(handerType, out List<Type> result))
                {
                    services.AddRange(result.Select(t => (dynamic)componentContext.Resolve(t)));
                }
            }
            services.AddRange(GetResolveServices(handerTypes));
            return services;
        }
        private List<Type> GetExpectTypes(Type genericInterfaceType, IEnumerable<Type> source)
        {
            List<Type> result = new List<Type>();
            foreach (Type type in source)
            {
                if (!type.IsGenericType)
                {
                    result.Add(type);
                }
                else
                {
                    var list = AutoFacHelper.GetGenericTypes(genericInterfaceType);
                    var list2 = AutoFacHelper.GetGenericTypes(type);
                    if (list.Except(list2).Any())
                    {
                        continue;
                    }
                    result.Add(type);
                }
            }
            return result;
        }

    }
}
